Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
63.64% covered (warning)
63.64%
7 / 11
CRAP
93.10% covered (success)
93.10%
108 / 116
AttributeUpdater
0.00% covered (danger)
0.00%
0 / 1
63.64% covered (warning)
63.64%
7 / 11
40.52
93.10% covered (success)
93.10%
108 / 116
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 update
0.00% covered (danger)
0.00%
0 / 1
3.47
62.50% covered (warning)
62.50%
5 / 8
 validateDataType
0.00% covered (danger)
0.00%
0 / 1
9.01
94.44% covered (success)
94.44%
17 / 18
 setData
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
29 / 29
 findAttributeGroup
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setValue
0.00% covered (danger)
0.00%
0 / 1
2.50
50.00% covered (danger)
50.00%
2 / 4
 setAvailableLocales
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
13 / 13
 setGroup
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
 setType
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
14 / 14
 validateDateFormat
0.00% covered (danger)
0.00%
0 / 1
4.25
75.00% covered (warning)
75.00%
6 / 8
 getDate
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
<?php
namespace Akeneo\Pim\Structure\Component\Updater;
use Akeneo\Channel\Component\Repository\LocaleRepositoryInterface;
use Akeneo\Pim\Structure\Component\AttributeTypeRegistry;
use Akeneo\Pim\Structure\Component\Model\AttributeGroupInterface;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
use Akeneo\Pim\Structure\Component\Repository\AttributeGroupRepositoryInterface;
use Akeneo\Tool\Component\Localization\TranslatableUpdater;
use Akeneo\Tool\Component\StorageUtils\Exception\InvalidObjectException;
use Akeneo\Tool\Component\StorageUtils\Exception\InvalidPropertyException;
use Akeneo\Tool\Component\StorageUtils\Exception\InvalidPropertyTypeException;
use Akeneo\Tool\Component\StorageUtils\Exception\UnknownPropertyException;
use Akeneo\Tool\Component\StorageUtils\Updater\ObjectUpdaterInterface;
use Doctrine\Common\Util\ClassUtils;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
/**
 * Updates an attribute.
 *
 * @author    Olivier Soulet <olivier.soulet@akeneo.com>
 * @copyright 2015 Akeneo SAS (http://www.akeneo.com)
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class AttributeUpdater implements ObjectUpdaterInterface
{
    /** @var AttributeGroupRepositoryInterface */
    protected $attrGroupRepo;
    /** @var LocaleRepositoryInterface */
    protected $localeRepository;
    /** @var AttributeTypeRegistry */
    protected $registry;
    /** @var PropertyAccessor */
    protected $accessor;
    /** @var TranslatableUpdater */
    protected $translatableUpdater;
    /** @var array */
    private $properties;
    /**
     * @param AttributeGroupRepositoryInterface $attrGroupRepo
     * @param LocaleRepositoryInterface         $localeRepository
     * @param AttributeTypeRegistry             $registry
     * @param TranslatableUpdater               $translatableUpdater
     * @param array                             $properties
     */
    public function __construct(
        AttributeGroupRepositoryInterface $attrGroupRepo,
        LocaleRepositoryInterface $localeRepository,
        AttributeTypeRegistry $registry,
        TranslatableUpdater $translatableUpdater,
        array $properties
    ) {
        $this->attrGroupRepo = $attrGroupRepo;
        $this->localeRepository = $localeRepository;
        $this->registry = $registry;
        $this->accessor = PropertyAccess::createPropertyAccessor();
        $this->translatableUpdater = $translatableUpdater;
        $this->properties = $properties;
    }
    /**
     * {@inheritdoc}
     */
    public function update($attribute, array $data, array $options = [])
    {
        if (!$attribute instanceof AttributeInterface) {
            throw InvalidObjectException::objectExpected(
                ClassUtils::getClass($attribute),
                AttributeInterface::class
            );
        }
        foreach ($data as $field => $value) {
            $this->validateDataType($field, $value);
            $this->setData($attribute, $field, $value);
        }
        return $this;
    }
    /**
     * Validate the data type of a field.
     *
     * @param string $field
     * @param mixed  $data
     *
     * @throws InvalidPropertyTypeException
     * @throws UnknownPropertyException
     */
    protected function validateDataType($field, $data)
    {
        if (in_array($field, ['labels', 'available_locales', 'allowed_extensions'])) {
            if (!is_array($data)) {
                throw InvalidPropertyTypeException::arrayExpected($field, static::class, $data);
            }
            foreach ($data as $key => $value) {
                if (null !== $value && !is_scalar($value)) {
                    throw InvalidPropertyTypeException::validArrayStructureExpected(
                        $field,
                        sprintf('one of the "%s" values is not a scalar', $field),
                        static::class,
                        $data
                    );
                }
            }
        } elseif (in_array(
            $field,
            array_merge([
                'code',
                'type',
                'group',
                'unique',
                'useable_as_grid_filter',
                'metric_family',
                'default_metric_unit',
                'reference_data_name',
                'max_characters',
                'validation_rule',
                'validation_regexp',
                'wysiwyg_enabled',
                'number_min',
                'number_max',
                'decimals_allowed',
                'negative_allowed',
                'date_min',
                'date_max',
                'max_file_size',
                'minimum_input_length',
                'sort_order',
                'localizable',
                'scopable',
                'required',
            ], $this->properties)
        )) {
            if (null !== $data && !is_scalar($data)) {
                throw InvalidPropertyTypeException::scalarExpected($field, static::class, $data);
            }
        } else {
            throw UnknownPropertyException::unknownProperty($field);
        }
    }
    /**
     * @param AttributeInterface $attribute
     * @param string             $field
     * @param mixed              $data
     *
     * @throws InvalidPropertyException
     * @throws UnknownPropertyException
     */
    protected function setData(AttributeInterface $attribute, $field, $data)
    {
        switch ($field) {
            case 'type':
                $this->setType($attribute, $data);
                break;
            case 'labels':
                $this->translatableUpdater->update($attribute, $data);
                break;
            case 'group':
                $this->setGroup($attribute, $data);
                break;
            case 'available_locales':
                $this->setAvailableLocales($attribute, $field, $data);
                break;
            case 'date_min':
                $this->validateDateFormat('date_min', $data);
                $date = $this->getDate($data);
                $attribute->setDateMin($date);
                break;
            case 'date_max':
                $this->validateDateFormat('date_max', $data);
                $date = $this->getDate($data);
                $attribute->setDateMax($date);
                break;
            case 'allowed_extensions':
                $attribute->setAllowedExtensions(implode(',', $data));
                break;
            default:
                if (in_array($field, $this->properties)) {
                    $attribute->setProperty($field, $data);
                } else {
                    $this->setValue($attribute, $field, $data);
                }
        }
    }
    /**
     * @param string $code
     *
     * @return AttributeGroupInterface|null
     */
    protected function findAttributeGroup($code)
    {
        $attributeGroup = $this->attrGroupRepo->findOneByIdentifier($code);
        return $attributeGroup;
    }
    /**
     * @param $attribute
     * @param $field
     * @param $data
     *
     * @throws UnknownPropertyException
     */
    protected function setValue($attribute, $field, $data)
    {
        try {
            $this->accessor->setValue($attribute, $field, $data);
        } catch (NoSuchPropertyException $e) {
            throw UnknownPropertyException::unknownProperty($field, $e);
        }
    }
    /**
     * @param AttributeInterface $attribute
     * @param string                                                   $field
     * @param array                                                    $availableLocaleCodes
     *
     * @throws UnknownPropertyException
     * @throws InvalidPropertyException
     */
    protected function setAvailableLocales(AttributeInterface $attribute, $field, array $availableLocaleCodes)
    {
        $locales = [];
        foreach ($availableLocaleCodes as $localeCode) {
            $locale = $this->localeRepository->findOneByIdentifier($localeCode);
            if (null === $locale) {
                throw InvalidPropertyException::validEntityCodeExpected(
                    'available_locales',
                    'locale code',
                    'The locale does not exist',
                    static::class,
                    $localeCode
                );
            }
            $locales[] = $locale;
        }
        $this->setValue($attribute, $field, $locales);
    }
    /**
     * @param AttributeInterface $attribute
     * @param string             $data
     *
     * @throws InvalidPropertyException
     */
    protected function setGroup(AttributeInterface $attribute, $data)
    {
        $attributeGroup = $this->findAttributeGroup($data);
        if (null === $attributeGroup) {
            throw InvalidPropertyException::validEntityCodeExpected(
                'group',
                'code',
                'The attribute group does not exist',
                static::class,
                $data
            );
        }
        $attribute->setGroup($attributeGroup);
    }
    /**
     * @param $attribute
     * @param $data
     *
     * @throws InvalidPropertyException
     */
    protected function setType($attribute, $data)
    {
        if (('' === $data) || (null === $data)) {
            throw InvalidPropertyException::valueNotEmptyExpected('type', static::class);
        }
        try {
            $attributeType = $this->registry->get($data);
        } catch (\LogicException $exception) {
            throw InvalidPropertyException::validEntityCodeExpected(
                'type',
                'attribute type',
                'The attribute type does not exist',
                static::class,
                $data
            );
        }
        $attribute->setType($attributeType->getName());
        $attribute->setBackendType($attributeType->getBackendType());
        $attribute->setUnique($attributeType->isUnique());
    }
    /**
     * Valid dates:
     * - "2015-12-31T00:00:00+01:00"
     * - "2015-12-31"
     *
     * Wrong dates:
     * - "2015/12/31"
     * - "2015-45-31"
     * - "not a date"
     *
     * @param string $field
     * @param string $data
     *
     * @throws InvalidPropertyException
     */
    protected function validateDateFormat($field, $data)
    {
        if (null === $data) {
            return;
        }
        try {
            new \DateTime($data);
        } catch (\Exception $e) {
            throw InvalidPropertyException::dateExpected($field, 'yyyy-mm-dd', static::class, $data);
        }
        if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $data)) {
            throw InvalidPropertyException::dateExpected($field, 'yyyy-mm-dd', static::class, $data);
        }
    }
    /**
     * @param string $date
     *
     * @return \DateTime|null
     */
    protected function getDate($date)
    {
        if (null === $date) {
            return null;
        }
        return new \DateTime($date);
    }
}